home *** CD-ROM | disk | FTP | other *** search
/ Developer Helper 1: Phil & Dave's Excellent CD / Excellent CD HFS.raw / Moof / Goodies / DTS Goodies / Hier⁄Popup Menu Demos / PopMenus.p < prev    next >
Text File  |  1989-04-13  |  12KB  |  332 lines

  1. {*************************************************************************
  2.  
  3. Pop-up Menu Example
  4.  
  5. This program is a simple example of how to use pop-up menus in your
  6. application. It implements a pop-up as a userItem in a modal dialog
  7. box (this is a helpful example in its own right!). A few tips:
  8.  
  9. The Menu Manager provides just one routine, PopUpMenuSelect, that you
  10. call when the user clicks on the current selection of your pop-up. Your 
  11. application is responsible for drawing the current selection (and the
  12. title that goes with it), and hilighting the title before calling
  13. PopUpMenuSelect, and unhilighting afterwards.
  14.  
  15. The metrics involved with drawing the current selection are a little
  16. tricky; this example does things in the recommended way. Note the
  17. following as you try the example (it helps to have a long font name
  18. to see some of the extreme cases!):
  19.  
  20. - There is one pixel between the title's boundsrect (as defined in the
  21.   DITL, a staticText item) and the popup's (a userItem). Also, the 
  22.   userItem's boundsrect (as defined) is used as the area INSIDE the
  23.   current selection box. This is the easiest way to handle this; it's
  24.   inconsistent in that normally, clicking on the drop shadow would get
  25.   you what you want (in this case, these clicks are ignored; the user
  26.   must click within the box). The only case where this is a problem
  27.   is if you filter the drawing of items in response to an update event
  28.   to those items that lie within the update region (if just the drop
  29.   shadow needed updating, it wouldn't be updated with this 
  30.   implementation).
  31.  
  32. - The current selection is always drawn in the same place & same
  33.   size. To prevent the selection from drawing wider than the box,
  34.   the selection is truncated and ellipses added (if necessary). This
  35.   algorithm may be handy elsewhere, too! This particular example has
  36.   an especially narrow current selection box (to show off the truncation
  37.   feature); you'll probably want to make yours wide enough to accomodate
  38.   most possible values.
  39.   
  40. - The title is highlighted while the pop-up is up (that is, during
  41.   PopUpMenuSelect), and presumably, during the duration of any operation
  42.   generated by the chosen item. This is intended to work just like the
  43.   hilighting of a regular menu title. 
  44.   
  45. [You need to have more than about seven font families in your system
  46. file to see this one:] A "feature" of the Menu Manager is that it must 
  47. call the menu defproc just once to allocate the space beneath the menu. 
  48. Also, the menu must be kept onscreen (scrolling if necessary). Also, 
  49. the human interface dictates that the currently-chosen item should 
  50. appear under the mouse when the pop-up is presented. These three 
  51. requirements conflict; note the case where the last item is the current
  52. selection.
  53.  
  54. The menu manager will be happy to put the current item whereever you
  55. say it should, but may leave white space in the menu if it's necessary
  56. to "pre-scroll" the menu to give the selection you asked for. This
  57. white space appears because the bits beneath the menu are saved and
  58. restored just once (each).
  59.  
  60. The upshot of all this is that this is a Menu Manager gotcha; there is
  61. no workaround, but the problem may be fixed in future Systems.
  62.  
  63. Pop-up menus must be InsertMenu'd just like hierarchical submenus; they
  64. should left in the menu list only while PopUpMenuSelect is being called.
  65. The example handles this correctly.
  66.  
  67. Applications' pop-up menus' IDs, should always be in the range 1 through 
  68. 235 (inclusive); desk accessories' pop-ups should use IDs 236 through
  69. 255. This really applies to all menus; it's most important for pop-up and
  70. submenus, however.
  71.  
  72. This particular example is meant to show the mechanics of creating and
  73. handling events from submenus; in the interest of simplicity, certain nice
  74. features (like checkmarking the current font) have been omitted.
  75.  
  76. Have fun.
  77.  
  78. *************************************************************************}
  79.  
  80. PROGRAM PopMenus;
  81. {*
  82.  * Pop-up Menu Example
  83.  * Bryan Stearns 05May87 
  84.  *}
  85.  
  86. USES MemTypes, Quickdraw, OSIntf, ToolIntf, PackIntf;
  87.  
  88. {$R-} {no range checking}
  89. {$D+} {Generate debug symbols}
  90.  
  91. CONST
  92.     myDLOGid = 128;            {our dialog template's resource ID}
  93.     popMenuID = 128;        {our menu's ID}
  94.     myALRTid = 129;         {our “need right machine & sys software” alert}
  95.     
  96.     {Items in our dialog box: there’s an OK button, and…}
  97.     iPopUp = OK+1;            {the Pop-up userItem}
  98.     iPopPrompt = iPopUp+1;    {the Prompt staticText}
  99.     iDefOKRing = iPopPrompt+1;    {the OK-button-default-ring userItem}
  100.  
  101.     {ASCII code for Return and Enter}
  102.     crCode = 13;             {(these are keyboard-independent)}
  103.     enterCode = 3;
  104.  
  105.     {constants for positioning the default item within its box}
  106.     leftSlop = 13;            {leave this much space on left of title}
  107.     rightSlop = 5;            {  this much on right}
  108.     botSlop = 5;            {  this much below baseline}
  109.     
  110. VAR    myDialog: DialogPtr;    {our dialog pointer}
  111.     popUpBox: Rect;            {boundsrect of our popUp's title box}
  112.     promptBox: Rect;        {boundsrect of its prompt}
  113.     popMenu: MenuHandle;    {our popUp's menu}
  114.     hitItem: INTEGER;        {result of ModalDialog}
  115.     lastChoice: INTEGER;    {the last-chosen item from the pop-up menu}
  116.     
  117.     theType: INTEGER;        {used as temp in GetDItem/SetDItem}
  118.     theHdl: Handle;            {used as temp in GetDItem/SetDItem}
  119.     theBox: Rect;            {used as temp in GetDItem/SetDItem}
  120.  
  121.  
  122. {*
  123.  * Draw our popUp’s current selection box
  124.  * Note: This is called by the Dialog Manager (for 
  125.  * update events) as well as our own Filterproc.
  126.  *}
  127. PROCEDURE DrawPopUp(theDialog: DialogPtr; theItem: INTEGER);
  128. VAR r: Rect;
  129.     curFont: Str255;
  130.     newWid, newLen, wid: INTEGER;
  131. BEGIN
  132.     GetItem(popMenu,lastChoice,curFont); {get currently-selected item}
  133.     r := popUpbox;
  134.     WITH r DO BEGIN
  135.         InsetRect(r,-1,-1); {make it a little bigger}
  136.         
  137.         {Make sure the title fits. Truncate it and add an ellipses (“…”)}
  138.         {if it doesn’t (by the way, “…” is option-semicolon)}
  139.         wid := (right - left) - (leftSlop + rightSlop); {available string area}
  140.         newWid := StringWidth(curFont); {get current width}
  141.         IF newWid > wid THEN BEGIN {doesn't fit - truncate it}
  142.             newLen := LENGTH(curFont); {current length in characters}
  143.             wid := wid - CharWidth('…'); {subtract width of ellipses}
  144.             
  145.             REPEAT {until it fits (or we run out of characters)}
  146.                 {drop the last character and its width}
  147.                 newWid := newWid - CharWidth(curFont[newLen]);
  148.                 newLen := PRED(newLen);
  149.             UNTIL (newWid <= wid) OR (LENGTH(curFont) = 0);
  150.             
  151.             {add the ellipses character}
  152.             newLen := SUCC(newLen); {one more char}
  153.             curFont[newLen] := '…'; {it’s the ellipses}
  154.             curFont[0] := CHR(newLen); {fix the length}
  155.         END;
  156.  
  157.         {draw the box and its drop shadow}
  158.         FrameRect(r);
  159.         MoveTo(right,top+2); LineTo(right,bottom);
  160.         LineTo(left+2,bottom);
  161.         
  162.         {draw the string}
  163.         MoveTo(left+LeftSlop,bottom-BotSlop);
  164.         DrawString(curFont);
  165.     END;
  166. END; {DrawPopUp}
  167.  
  168.  
  169. {*
  170.  * Draw the ring around the OK button, the way that
  171.  * alert boxes get it. This procedure is only called 
  172.  * by the Dialog Manager for update events.
  173.  *}
  174. PROCEDURE DrawOKDefault(theDialog: DialogPtr; theItem: INTEGER);
  175. VAR savePen: PenState;
  176. BEGIN
  177.     GetPenState(savePen); {save the old pen state}
  178.     
  179.     GetDItem(theDialog, theItem, theType, theHdl, theBox); {get the item’s rect}
  180.     PenSize(3,3); {make the pen fatter}
  181.     FrameRoundRect(theBox,16,16); {draw the ring}
  182.  
  183.     SetPenState(savePen); {restore the pen state}
  184. END; {DrawOKDefault}
  185.  
  186.  
  187. {*
  188.  * Filterproc for our dialog box
  189.  * - supports Enter & Return --> OK.
  190.  * - watches for userItem hits
  191.  *}
  192. FUNCTION myFilter(theDialog: DialogPtr; VAR theEvent: EventRecord; VAR itemHit: INTEGER): BOOLEAN;
  193. VAR    mouseLoc, popLoc: Point;
  194.     newChoice: INTEGER;
  195.     chosen,ignoreLong: LongInt;
  196. BEGIN    
  197.     itemHit := 0; {We return these two values. Initialize them.}
  198.     myFilter := FALSE;
  199.     SetPort(theDialog);
  200.     
  201.     WITH theEvent DO CASE what OF
  202.         keyDown: BEGIN
  203.             IF (theEvent.message MOD 256) IN [crCode, enterCode] THEN BEGIN
  204.                 {user pressed Return or Enter}
  205.                 GetDItem(theDialog, OK, theType, theHdl, theBox); {get the button's rect}
  206.                 HiliteControl(ControlHandle(theHdl), 1); {make it look...}
  207.                 Delay(3, ignoreLong); {...like the OK button was hit}
  208.                 
  209.                 myFilter := TRUE; {dialog is over}
  210.                 itemHit := OK; {have ModalDialog return that the user hit OK}
  211.             END;
  212.         END; {keydown case}
  213.         
  214.         mouseDown: BEGIN {"Click!"}
  215.             mouseLoc := where; {copy the mouse position}
  216.             GlobalToLocal(mouseLoc); {convert it to local coordinates}
  217.             
  218.             {Was the click in our item?}
  219.             IF (FindDItem(theDialog, mouseLoc) + 1) = iPopUp THEN BEGIN {Yep, the click was ours!}
  220.             
  221.                 {We're going to pop up our menu. Insert our menu into the menu list,}
  222.                 {then call CalcMenuSize (to work around a bug in the Menu Manager), }
  223.                 {then call PopUpMenuSelect and let the user drag around. Note that the}
  224.                 {(top,left) parameters to PopUpMenuSelect are our item’s, converted to}
  225.                 {global coordinates.}
  226.                 InvertRect(promptBox); {hilight the prompt}
  227.                 InsertMenu(popMenu,-1); {insert our menu in the menu list}
  228.                 popLoc := popUpBox.topLeft; {copy our item’s topleft}
  229.                 LocalToGlobal(popLoc); {convert back to global coords}
  230.                 CalcMenuSize(popMenu); {Work around Menu Mgr bug}
  231.                 WITH popLoc DO chosen := PopUpMenuSelect(popMenu, v, h, lastChoice);
  232.                 InvertRect(promptBox); {unhilight the prompt}
  233.                 DeleteMenu(popMenuID); {remove our menu from the menu list}
  234.                 
  235.                 {Was something chosen?}
  236.                 IF chosen <> 0 THEN BEGIN {yep, something was chosen}
  237.                     newChoice := LoWord(chosen); {get the chosen item number}
  238.                     
  239.                     IF newChoice <> lastChoice THEN BEGIN
  240.                         {the user chose an item other than the current one}
  241.                         SetItemMark(popMenu,lastChoice,' '); {unmark the old choice}
  242.                         SetItemMark(popMenu,newChoice,CHR(checkMark)); {mark the new choice}
  243.                         lastChoice := newChoice; {update the current choice}
  244.                         
  245.                         {Draw the new title}
  246.                         EraseRect(popUpBox);
  247.                         DrawPopUp(theDialog,iPopUp);
  248.                         
  249.                         myFilter := TRUE; {dialog is over}
  250.                         itemHit := iPopUp; {have ModalDialog return that the user changed items}
  251.                     END; {if this choice was not the current choice}
  252.                 END; {if something was chosen}
  253.  
  254.             END; {if clicked in our userItem}
  255.         END; {mousedown case}
  256.     END {case}
  257. END; {myFilter}
  258.  
  259.  
  260. {*
  261.  * Main
  262.  *}
  263. BEGIN
  264.     {Initialize all the usual managers}
  265.     InitGraf(@thePort);    
  266.     InitFonts;            
  267.     FlushEvents(everyEvent,0);
  268.     InitWindows;                
  269.     InitMenus;            
  270.     TEInit;
  271.     InitDialogs(NIL);            
  272.     InitCursor;            
  273.     
  274.     {Check to make sure we’re running on a machine}
  275.     {capable of supporting pop-up menus}
  276.     (** SysEnvirons(xxx); **)
  277.     IF FALSE THEN BEGIN {Sorry, see your dealer}
  278.         hitItem := StopAlert(myALRTid,NIL); {put up the alert}
  279.         ExitToShell; {back to the finder}
  280.     END;
  281.     
  282.     {Get a menu containing the current set of fonts}
  283.     popMenu := NewMenu(popMenuID,'notUsed'); {Create a menu (its title is ignored)}
  284.     AddResMenu(popMenu,'FONT'); {fill it with fonts}
  285.     lastChoice := 1; {make the first item the default}
  286.     SetItemMark(popMenu,1,CHR(checkMark)); {check it}
  287.     
  288.     {Get our dialog box, and set up our useritem (the dialog template}
  289.     {defines this dialog to be hidden, so that it won’t be drawn until}
  290.     {we’ve installed our userItem’s drawing procedure)}
  291.     myDialog := GetNewDialog(myDLOGid,NIL,POINTER(-1));
  292.         
  293.     {Find out where our popUp's rectangle is, and set its item handle to be}
  294.     {a pointer to our popup-drawing procedure}
  295.     GetDItem(myDialog,iPopUp,theType,theHdl,popUpbox); {get the rect}
  296.     SetDItem(myDialog,iPopUp,theType,@DrawPopUp,popUpbox); {set the procPtr}
  297.     
  298.     {Find out where the prompt for our popUp is, so that we can invert its}
  299.     {rect when popping up our menu}
  300.     GetDItem(myDialog,iPopPrompt,theType,theHdl,promptBox); {get the rect}
  301.     
  302.     {Move our default-OK-button userItem to around the OK button, and set its}
  303.     {item handle to be a pointer to our other drawing procedure}
  304.     GetDItem(myDialog,OK,theType,theHdl,theBox); {get the OK button's rect}
  305.     InsetRect(theBox,-4,-4); {make the rect a little bigger}
  306.     {set the same old type, our procptr, and the new box}
  307.     SetDItem(myDialog,iDefOKRing,userItem+itemDisable,@DrawOKDefault,theBox);
  308.  
  309.     ShowWindow(myDialog); {show it, finally!}
  310.     
  311.     REPEAT
  312.     
  313.         ModalDialog(@myFilter,hitItem);
  314.         
  315.         CASE hitItem OF        {which item was hit, if any?}
  316.         
  317.             iPopUp: BEGIN        {he chose a new item in our pop-up}
  318.                 {Do whatever needs to be done when the choice changes; }
  319.                 {the on-screen current value box has already been updated.}
  320.             END; {iPopUp case}
  321.             
  322.             {other items here}
  323.             
  324.         END; {case}
  325.         
  326.     UNTIL hitItem = OK;
  327.     
  328.     HideWindow(myDialog); {hide the window, as feedback}
  329. END.
  330.  
  331.  
  332.